﻿namespace EFS.EventLog.Loggers
{
	/// Class StringList
	public sealed class AsyncLog : ILogger, System.IDisposable
	{
		#region Components

		// message
		private class Message { public System.String Text; public System.Boolean File; public System.Boolean Email; public System.Boolean Console; }

		// sync access
		private System.Object syncAccess = new System.Object();

		// wait start
		private System.Threading.ManualResetEvent waitStartEvent = null;

		// wait stop
		private System.Threading.ManualResetEvent waitStopEvent = null;

		// wait write
		private System.Threading.ManualResetEvent waitWriteEvent = null;

		// stop flag
		private System.Int32 stop = 0;

		// log content
		private System.Collections.Generic.List<Message> items = new System.Collections.Generic.List<Message>();

		// log thread
		private System.Threading.Thread logThread = null;

		#endregion

		#region Constructors

		/// <summary>
		/// Static constructor to init local instance
		/// </summary>
		static AsyncLog()
		{
			Instance = new AsyncLog();
		}

		/// <summary>
		/// Contructor
		/// </summary>
		private AsyncLog()
		{
		}

		#endregion

		#region Usage

		/// Property Instance (singleton): Get
		public static AsyncLog Instance { get; private set; }

		/// <summary>
		/// Send message
		/// </summary>
		/// <param name="messageText"></param>
		/// <param name="file"></param>
		/// <param name="email"></param>
		/// <param name="console"></param>
		public void Add(System.String messageText, System.Boolean file, System.Boolean email, System.Boolean console)
		{
			// add to collection
			lock (syncAccess)
			{
				// check
				if (null == logThread)
				{
					throw new System.InvalidOperationException("AsyncLogger not started");
				}

				// check piling out
				if (items.Count > 10)
				{
					items = new System.Collections.Generic.List<Message>();
				}

				// add
				items.Add(new Message() { Text = messageText, File = file, Email = email, Console = console });

				// signal thread
				waitWriteEvent.Set();
			}
		}

		/// <summary>
		/// Start writter, wait to start
		/// </summary>
		public void StartWritter()
		{
			// only one can start log
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// must not have thread
				if (null != logThread)
				{
					throw new System.InvalidOperationException("AsyncLogger thread already started");
				}

				// reset stop
				stop = 0;

				// sanity check
				EFS.Common.Verify.Condition(null == waitStartEvent, "AsyncLogger Unexpected state waitStartEvent != null");

				// make event
				waitStartEvent = new System.Threading.ManualResetEvent(false);

				// must be null
				EFS.Common.Verify.Condition(null == waitWriteEvent, "AsyncLogger Wrong waitWriteEvent state: should be null");

				// create new wait event
				waitWriteEvent = new System.Threading.ManualResetEvent(false);

				// create new thread
				logThread = new System.Threading.Thread(new System.Threading.ThreadStart(WritterThreadProc));
				logThread.Start();

				// wait start
				waitStartEvent.WaitOne();

				// release
				waitStartEvent.Close();

				// clean
				waitStartEvent = null;
			}
		}

		/// Stop
		public void StopWritter()
		{
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// must have thread
				if (null == logThread)
				{
					throw new System.InvalidOperationException("AsyncLogger thread not started");
				}

				// check state
				if (logThread.ThreadState == System.Threading.ThreadState.Running ||
					logThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
				{
					// sanity check
					EFS.Common.Verify.Condition(null == waitStopEvent, "AsyncLogger Unexpected state waitStopEvent != null");

					// make event
					waitStopEvent = new System.Threading.ManualResetEvent(false);

					// set signal to thread
					stop = 1;

					// create new wait event
					waitWriteEvent.Set();

					// wait stop
					if (waitStopEvent.WaitOne(500))
					{
						// abort
						logThread.Abort();
					}

					// release
					waitStopEvent.Close();

					// clean
					waitStopEvent = null;
				}

				// let GC take care of it
				logThread = null;
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Working thread procedure
		/// </summary>
		private void WritterThreadProc()
		{
			// check if some one is wating
			if (null != waitStartEvent)
			{
				// wait set
				waitStartEvent.Set();
			}

			// cicle
			while (stop != 1)
			{
				try
				{
					// my temp list
					System.Collections.Generic.List<Message> itemsTemp = null;

					// add to collection locked while we swith
					// EFS.EventLog.Writers.File.Instance.Write(EFS.EventLog.EventDescriptor.Extended("Entering Lock WritterThreadProc EFS.EventLog.Loggers", new System.Diagnostics.StackTrace()));
					lock (syncAccess)
					{
						// swap
						itemsTemp = items;
						items = new System.Collections.Generic.List<Message>();
					}

					// cicle all and write to log
					foreach (Message message in itemsTemp)
					{
						// file
						if (message.File)
						{
							// Write log
							EFS.EventLog.Writers.File.Instance.Write(message.Text);
						}

						// email
						if (message.Email)
						{
							EFS.EventLog.Writers.Email.Instance.Write(message.Text);
						}

						// console
						if (message.Console)
						{
							EFS.EventLog.Writers.Console.Instance.Write(message.Text);
						}
					}

					// test if we must wait
					if (0 == itemsTemp.Count && stop != 1)
					{
						// Wait a Set
						waitWriteEvent.WaitOne(500);

						// Reset for next wait
						waitWriteEvent.Reset();
					}
				}
				catch (System.Threading.ThreadAbortException)
				{
					// bye
					break;
				}
				catch (System.Exception)
				{}
			}

			// check if some one is wating
			if (null != waitStopEvent)
			{
				// wait set
				waitStopEvent.Set();
			}
		}

		#endregion

		#region Destructor
		
		/// <summary>
		/// Disponse
		/// </summary>
		public void Dispose()
		{
			try
			{
				if (waitStartEvent != null)
				{
					waitStartEvent.Close();
					//waitStartEvent.Dispose();
					waitStartEvent = null;
				}

				if (waitStopEvent != null)
				{
					waitStopEvent.Close();
					//waitStopEvent.Close();
					waitStopEvent = null;
				}

				if (waitWriteEvent != null)
				{
					waitWriteEvent.Close();
					//waitWriteEvent.Close();
					waitWriteEvent = null;
				}
			}
			catch (System.Exception ex)
			{
				EFS.EventLog.Log.LogException(ex, null);
			}
		}

		/// <summary>
		/// Destructor
		/// </summary>
		~AsyncLog()
		{
			Dispose();
		}

		#endregion
	}
}